package net.wicp.tams.commons.apiext;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.io.filefilter.SuffixFileFilter;

import javassist.ClassPool;
import javassist.NotFoundException;
import lombok.extern.slf4j.Slf4j;

/**
 * 
 * @author 偏锋书生
 *
 */
@Slf4j
public class ClassLoaderPlugin {
	private URLClassLoader classLoader;

	public URLClassLoader getClassLoader() {
		return classLoader;
	}

	private ClassPool pool = ClassPool.getDefault();

	private Set<String> jars = new HashSet<String>();

	public Set<String> getJars() {
		return jars;
	}

	public ClassPool getPool() {
		return pool;
	}

	public ClassLoaderPlugin(String jarfileDir, ClassLoader parent) {
		this(new File(jarfileDir), parent);
	}

	public ClassLoaderPlugin(File jarfileDir) {
		this(jarfileDir, null);
	}

	public ClassLoaderPlugin(File jarfileDir, ClassLoader parent) {
		this.classLoader = createClassLoader(jarfileDir, parent);
	}

	public Class<?> loadClass(String className) throws ClassNotFoundException {

		return classLoader.loadClass(className);
	}

	public void addToClassLoader(final String baseDir, final FileFilter filter) {
		File base = new File(baseDir);
		if (base != null && base.exists() && base.isDirectory()) {
			File[] files = base.listFiles(filter);
			if (files == null || files.length == 0) {
				log.warn("No files added to classloader from lib: " + baseDir + " (resolved as: "
						+ base.getAbsolutePath() + ").");
			} else {
				this.classLoader = replaceClassLoader(classLoader, base, filter);
			}
		} else {
			log.warn("Can't find (or read) directory to add to classloader: " + baseDir + " (resolved as: "
					+ base.getAbsolutePath() + ").");
		}
	}

	public void addToClassLoader(final String baseDir) {
		addToClassLoader(baseDir, new SuffixFileFilter(".jar"));
	}

	private URLClassLoader createClassLoader(final File libDir, ClassLoader parent) {
		if (null == parent) {
			parent = Thread.currentThread().getContextClassLoader();
		}
		return replaceClassLoader(URLClassLoader.newInstance(new URL[0], parent), libDir, new SuffixFileFilter(".jar"));
	}

	private URLClassLoader replaceClassLoader(final URLClassLoader oldLoader, final File base,
			final FileFilter filter) {

		if (null != base && base.canRead() && base.isDirectory()) {
			File[] files = base.listFiles(filter);

			if (null == files || 0 == files.length) {
				log.error("replaceClassLoader base dir:{} is empty", base.getAbsolutePath());
				return oldLoader;
			}

			log.error("replaceClassLoader base dir: {} ,size: {}", base.getAbsolutePath(), files.length);

			URL[] oldElements = oldLoader.getURLs();
			URL[] elements = new URL[oldElements.length + files.length];
			System.arraycopy(oldElements, 0, elements, 0, oldElements.length);

			for (int j = 0; j < files.length; j++) {
				try {
					URL element = files[j].toURI().normalize().toURL();
					elements[oldElements.length + j] = element;
					jars.add(files[j].getPath());
					log.info("Adding '{}' to classloader", element.toString());
					pool.appendClassPath(files[j].getPath());
					log.info("Adding '{}' to pool", element.toString());
				} catch (MalformedURLException e) {
					log.error("load jar file error", e);
					throw new RuntimeException("load jar file error ");
				} catch (NotFoundException e) {
					log.error("add jar to pool error", e);
					throw new RuntimeException("pool addpath error ");
				}
			}
			ClassLoader oldParent = oldLoader.getParent();
			try {
				oldLoader.close();
			} catch (IOException e) {
				log.error("close classload error ", e);
			}
			return URLClassLoader.newInstance(elements, oldParent);
		}

		return oldLoader;
	}

}
